#pragma once

#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
#include "../dependencies/hiredis/hiredis.h"

namespace CwRedis {

    class RedisResult {

    public:

        RedisResult() = default;

        /**
          * @brief  通过一个redisReply指针构造一个RedisResult对象
          * @note   此时该指针的生命周期将由构造的RedisResult对象管理，请勿手动释放!
          * @note   第二个参数为对象析构时是否自动释放指针内存
          * @param  redisReply指针和是否自动删除
          */
        explicit RedisResult(redisReply *, bool deleteable = true);

        ~RedisResult() = default;

        RedisResult(const RedisResult &) = default;

        RedisResult(RedisResult &&) noexcept = default;

        RedisResult &operator=(const RedisResult &) = default;

        /**
          * @brief  获取redis返回类型
          * @retval 与hiredis返回值类型相同
          */
        int getType() const;

        /**
          * @brief  检查是否存在错误
          * @retval 如果返回的指针为空或指令异常将返回true
          */
        bool isError() const;

        /**
          * @brief  获取异常字符串
          * @retval 异常字符串
          */
        std::string getError() const;

        /**
          * @brief  将结果转换为字符串
          * @note   如果结果指针为空或类型不可转换将抛出std::runtime_error异常
          * @retval 字符串
          */
        std::string toString() const;

        /**
          * @brief  将结果转换为long long整形
          * @note   如果结果指针为空或类型不可转换将抛出std::runtime_error异常
          * @retval long long整形
          */
        long long toInteger() const;

        /**
          * @brief  检查结果是否为nil空
          * @note   如果结果指针为空或类型不可转换将抛出std::runtime_error异常
          * @retval 是否为空
          */
        bool isNil() const;

        /**
          * @brief  将结果转换为一个vector数组
          * @note   如果结果指针为空或类型不可转换将抛出std::runtime_error异常
          * @retval 一个vector数组
          */
        std::vector<RedisResult> toArray() const;

    private:

        // 使用智能指针管理redis回复的指针
        std::shared_ptr<redisReply> reply_;

    };

    RedisResult::RedisResult(redisReply *reply, bool deleteable) {
        if (deleteable) {
            reply_.reset(reply, [](redisReply *&reply) {
                if (reply != nullptr) {
                    freeReplyObject(reply);
                    reply = nullptr;
                }
            });
        } else {
            reply_.reset(reply, [](redisReply *&reply) {});
        }
    }

    bool RedisResult::isError() const {
        if (reply_ == nullptr || reply_->type == REDIS_REPLY_ERROR) {
            return true;
        }
        return false;
    }

    std::string RedisResult::getError() const {
        if (reply_ == nullptr) {
            throw std::runtime_error("empty ptr redis reply");
        }
        return reply_->str;
    }

    std::string RedisResult::toString() const {
        if (reply_ == nullptr) {
            throw std::runtime_error("empty ptr redis reply");
        }
        if (reply_->type != REDIS_REPLY_STRING) {
            throw std::runtime_error("illegal type conversions to string");
        }
        return reply_->str;
    }

    long long RedisResult::toInteger() const {
        if (reply_ == nullptr) {
            throw std::runtime_error("empty ptr redis reply");
        }
        if (reply_->type != REDIS_REPLY_INTEGER) {
            throw std::runtime_error("illegal type conversions to integer");
        }
        return reply_->integer;
    }

    bool RedisResult::isNil() const {
        if (reply_ == nullptr) {
            throw std::runtime_error("empty ptr redis reply");
        }
        if (reply_->type != REDIS_REPLY_NIL) {
            throw std::runtime_error("illegal type conversions to nil");
        }
        return reply_->type == REDIS_REPLY_NIL;
    }

    std::vector<RedisResult> RedisResult::toArray() const {
        if (reply_ == nullptr) {
            throw std::runtime_error("empty ptr redis reply");
        }
        if (reply_->type != REDIS_REPLY_ARRAY) {
            throw std::logic_error("illegal type conversions to array");
        }
        std::vector<RedisResult> ret;
        for (size_t i = 0; i < reply_->elements; ++i) {
            ret.push_back(RedisResult((redisReply *) reply_->element[i], false));
        }
        return ret;
    }

    int RedisResult::getType() const {
        if (reply_ == nullptr) {
            throw std::runtime_error("empty ptr redis reply");
        }
        return reply_->type;
    }

}
